-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Implement logic orchestration for Git Pull/Push operations #13518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
jabgui/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java
Outdated
Show resolved
Hide resolved
@@ -27,7 +27,7 @@ public interface LibraryTabContainer { | |||
* Closes a designated libraryTab | |||
* | |||
* @param tab to be closed. | |||
* @return true if closing the tab was successful | |||
* @return true if closing the tab was isSuccessful |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IntelliJ probably replaced this
* @return true if closing the tab was isSuccessful | |
* @return true if closing the tab was successful |
…esolver interface to the logic module) JabRef#12350
8302ebc
to
32c30a0
Compare
…remote main branch exists JabRef#12350
// Status is BEHIND or DIVERGED | ||
try (Git git = gitHandler.open()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment is trivial and can be derived from the code context. The status check above clearly indicates this is for BEHIND or DIVERGED cases.
// Debug hint: Show the created git graph on the command line | ||
// git log --graph --oneline --decorate --all --reflog |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented code should be removed as per project guidelines. Git history should be used instead of keeping debug hints in comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will remove in the end
…jabref into clean-gsoc-git-support-init
…jabref into clean-gsoc-git-support-init
import static org.mockito.Mockito.when; | ||
|
||
class GitSyncServiceTest { | ||
private Git git; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe name this aliceGit
to be consistent with @BeforeEach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it, I think private Git git
was from an earlier test setup. Later on, aliceGit was introduced and assigned to it, which made the git field redundant. Now I removed it and consistently use aliceGit, bobGit, and remoteGit to clearly distinguish the roles of each repository
@@ -113,25 +114,27 @@ void aliceBobSimple(@TempDir Path tempDir) throws Exception { | |||
|
|||
// Alice clone remote -> local repository | |||
aliceDir = tempDir.resolve("alice"); | |||
aliceGit = Git.cloneRepository() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, this was OK.
Maybe, you need to close aliceGit
at the end of the method.
@trag-bot didn't find any issues in the code! ✅✨ |
BibEntry local = conflict.local(); | ||
BibEntry remote = conflict.remote(); | ||
|
||
// Create Dialog + Set Title + Configure Diff Highlighting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment is trivial and only describes what the code does without adding any additional information or reasoning. The operations are self-evident from the code that follows.
} catch (JabRefException e) { | ||
dialogService.showErrorDialogAndWait("Git Pull Failed", e); | ||
// TODO: error handling | ||
} catch (GitAPIException e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrapping exceptions in RuntimeException loses the specific error context and makes error handling less effective. Should use more specific exception handling.
} | ||
|
||
public MergeResult pull() throws IOException, GitAPIException, JabRefException { | ||
MergeResult result = syncService.fetchAndMerge(bibFilePath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The operation might be long-running and should use BackgroundTask instead of direct execution to prevent UI freezing during Git operations.
|
||
@Override | ||
public Optional<BibDatabaseContext> resolveConflicts(List<ThreeWayEntryConflict> conflicts, BibDatabaseContext remote) { | ||
List<BibEntry> resolved = new ArrayList<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using ArrayList constructor directly instead of modern Java collections. Consider using List.of() for initial empty list creation to follow modern Java practices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But mutable list is required here, cannot use List.of()
jablib/src/main/java/org/jabref/logic/git/conflicts/SemanticConflictDetector.java
Show resolved
Hide resolved
BibEntry baseEntry = baseMap.get(key); | ||
|
||
if (baseEntry == null) { | ||
// New entry (not in base) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment does not add new information and simply restates what is obvious from the code. Such comments should be removed.
BibEntry base, | ||
BibEntry local, | ||
BibEntry remote |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The record fields lack validation to ensure none of the BibEntry parameters can be null. This could lead to NullPointerExceptions during conflict resolution operations.
import org.jabref.model.util.DummyFileUpdateMonitor; | ||
|
||
public class GitBibParser { | ||
public static BibDatabaseContext parseBibFromGit(String bibContent, ImportFormatPreferences importFormatPreferences) throws JabRefException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method should return Optional instead of potentially null BibDatabaseContext to follow modern Java practices for null safety.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
result.getDatabaseContext()
ensures that null values are never returned
import org.jspecify.annotations.NonNull; | ||
|
||
public class GitFileReader { | ||
// Unit test is in the GitSyncServiceTest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment provides no additional information beyond what can be inferred from the code and test location. It should be removed as it doesn't add value or reasoning.
|
||
public class GitFileReader { | ||
// Unit test is in the GitSyncServiceTest | ||
public static String readFileFromCommit(Git git, RevCommit commit, @NonNull Path relativePath) throws JabRefException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method returns String which could be null. Modern Java practices suggest using Optional for potentially null returns to make the API more explicit.
SelfContainedSaveConfiguration saveConfiguration = new SelfContainedSaveConfiguration(); | ||
Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); | ||
|
||
synchronized (bibDatabaseContext) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Synchronizing on a parameter object is dangerous as it can lead to deadlocks if the same object is used elsewhere. Consider using a private final lock object or more fine-grained synchronization.
// assumes the remote branch is 'origin/main' | ||
ObjectId headId = repo.resolve(HEAD); | ||
// and uses the default remote tracking reference | ||
// does not support multiple remotes or custom remote branch names so far |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments are trivial and only restate what is obvious from the code itself. They don't provide additional information about the reasoning or implementation details.
* @param local the current local branch tip | ||
* @param remote the tip of the remote tracking branch (typically origin/main) | ||
*/ | ||
public record RevisionTriple(RevCommit base, RevCommit local, RevCommit remote) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameters should be validated for null values since they represent critical commit references. The record should include validation in a compact constructor to prevent null values.
// 1. make a copy of the remote database | ||
BibDatabase newDatabase = new BibDatabase(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment restates what's obvious from the code without providing additional insight or reasoning behind the operation.
} | ||
|
||
@Override | ||
public MergeResult merge(BibDatabaseContext base, BibDatabaseContext local, BibDatabaseContext remote, Path bibFilePath) throws IOException, IOException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method parameters should be validated for null values before use. Currently there are no null checks which could lead to NullPointerException during execution.
applyPatchToDatabase(local, plan.fieldPatches()); | ||
|
||
for (BibEntry newEntry : plan.newEntries()) { | ||
BibEntry clone = (BibEntry) newEntry.clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Object.clone() is discouraged as per Effective Java. Should implement a proper copy constructor or factory method instead to ensure proper object copying.
import org.jabref.logic.bibtex.comparator.BibEntryDiff; | ||
|
||
public record MergeResult(boolean isSuccessful, List<BibEntryDiff> conflicts) { | ||
private static boolean SUCCESS = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SUCCESS constant should be declared as final since it's a static field that represents an immutable state. This follows Java best practices for constant values.
UP_TO_DATE, // Local and remote are in sync | ||
BEHIND, // Local is behind remote, pull needed | ||
AHEAD, // Local is ahead of remote, push needed | ||
DIVERGED, // Both local and remote have new commits; merge required | ||
CONFLICT, // Merge conflict detected | ||
UNTRACKED, // Not under Git control | ||
UNKNOWN // Status couldn't be determined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments for enum values are trivial and simply restate what can be derived from the enum names. These comments don't provide additional information about implementation details or reasoning.
Closes #12350
Final PR based on draft: JabRef#714
This PR implements the logic-layer orchestration for Git push and pull operations with semantic merging support. It enables JabRef to semantically merge .bib files when possible, avoiding manual conflict resolution for simple changes.
This is part of ongoing work to support full Git-based collaboration for .bib files. Further tests and UI integration are planned.
Steps to test
This PR implements the first part of Git sync support by enabling automatic semantic merges when there are no conflicts. The scenario is tested via TDD in GitSyncServiceTest, simulating the following steps:
Since they changed different entries, we expect a clean merge without user intervention. The orchestrator is GitSyncService, and helper utilities and value objects are temporarily located in org.jabref.logic.git.util.
Documentation Supplement
🧩 Table: Git-related Class Responsibilities
GitSyncService
GitHandler
🔧 Key Methods in
GitSyncService
fetchAndMerge(Path)
performSemanticMerge(...)
.bib
content semantically. UsesSemanticMerger
and conflict resolution strategy.push(Path)
📦 Package:
org.jabref.logic.git.io
This package bridges Git and JabRef's BibTeX data model by handling read/write operations for
.bib
files at specific Git revisions.GitBibParser
.bib
content from a Git commit into aBibDatabaseContext
using JabRef's parser.GitFileReader
.bib
text from a specific Git commit.GitFileWriter
BibDatabaseContext
content back to a.bib
file after merge.GitRevisionLocator
base
,local
, andremote
commits for 3-way merge.RevisionTriple
base
,local
, andremote
revisions.📦 Package:
org.jabref.logic.git.status
Handles detection of Git repository and file tracking/sync status. Used before pull/push to determine the correct operation.
GitStatusChecker
.bib
file and return a snapshot.GitStatusSnapshot
SyncStatus
UP_TO_DATE
,BEHIND
,DIVERGED
, etc.).📦 Package:
org.jabref.logic.git.conflicts
Handles semantic conflict detection and resolution strategies during 3-way merge.
SemanticConflictDetector
BibDatabaseContext
s.ThreeWayEntryConflict
GitConflictResolverStrategy
CliConflictResolverStrategy
Conflict Resolution Flow:
Detect conflicts with
SemanticConflictDetector
.If conflicts exist, invoke a
GitConflictResolverStrategy
:In GUI: use
GuiConflictResolverStrategy
In future CLI: use
CliConflictResolverStrategy
Apply merge plan if resolution succeeds.
📦 Package:
org.jabref.logic.git.merge
Executes semantic 3-way merge operations on
.bib
files, including building merge plans and applying field-level changes.MergePlan
SemanticMerger
MergePlan
to a localBibDatabaseContext
.GitSemanticMergeExecutor
performSemanticMerge()
contract.GitSemanticMergeExecutorImpl
GitMergeUtil
💡 GUI Module Integration Note
GUI dependencies are temporarily introduced to reuse the existing
MergeEntriesDialog
for conflict resolution.This is injected via
GitConflictResolverStrategy
to keep the dependency direction correct (logic → gui).A major UI refactor is WIP.
GuiConflictResolverStrategy
MergeEntriesDialog
to let users manually resolve semantic conflicts inBibEntry
s.Mandatory checks
CHANGELOG.md
described in a way that is understandable for the average user (if change is visible to the user)